package com.nio.demo;

/**
 * Created by 峥桂 on 2016/8/23.
 */

import com.nio.service.TCPProtocol;
import com.nio.service.TCPProtocolImpl;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class NIOSServer {
    private int port = 8888;

    //解码buffer
    private Charset cs = Charset.forName("utf-8");

    // 缓冲区大小
    private static final int BufferSize = 1024;
    // 超时时间，单位毫秒
    private static final int TimeOut = 3000;

    /*接受数据缓冲区*/
    private static ByteBuffer sBuffer = ByteBuffer.allocate(1024);

    /*映射客户端channel */
    private Map<String, SocketChannel> clientsMap = new HashMap<String, SocketChannel>();

    private static Selector selector;

    public NIOSServer(int port) {
        this.port = port;
        try {
            init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void init() throws IOException {
        /*
         *启动服务器端，配置为非阻塞，绑定端口，注册accept事件
         *ACCEPT事件：当服务端收到客户端连接请求时，触发该事件
         */
        // 打开监听信道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        // 与本地端口绑定
        serverSocket.bind(new InetSocketAddress(port));
        // 创建选择器
        selector = Selector.open();
        // 将选择器绑定到监听信道,只有非阻塞信道才可以注册选择器.并在注册过程中指出该信道可以进行Accept操作
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("server start on port:" + port);
    }

    /**
     * 服务器端轮询监听，select方法会一直阻塞直到有相关事件发生或超时
     */
    private void listen() {
        // 创建一个处理协议的实现类,由它来具体操作
        TCPProtocol protocol = new TCPProtocolImpl(BufferSize);
        while (true) {
            try {
//                selector.select();//返回值为本次触发的事件数
                // 等待某信道就绪(或超时)
                if (selector.select(TimeOut) == 0) {// 监听注册通道，当其中有注册的 IO
                    // 操作可以进行时，该函数返回，并将对应的
                    // SelectionKey 加入 selected-key
                    // set
                    System.out.print("独自等待.");
                    continue;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
//                Iterator<SelectionKey> keyIter = selectionKeys.iterator();
                for(SelectionKey key:selectionKeys){
                    try {
                        handle(key, protocol);
                    } catch (Exception e) {
                        // 出现IO异常（如客户端断开连接）时移除处理过的键
//                        keyIter.remove();
                        continue;
                    }
                }
                selectionKeys.clear();//清除处理过的事件

            } catch (Exception e) {
                e.printStackTrace();
                break;
            }

        }
    }

    /**
     * 处理不同的事件
     */
    private void handle(SelectionKey selectionKey, TCPProtocol protocol) throws IOException {
        if (selectionKey.isAcceptable()) {
            protocol.handleAccept(selectionKey);
        } else if (selectionKey.isReadable()) {
            protocol.handleRead(selectionKey);
        }
//        if (selectionKey.isValid() && selectionKey.isWritable()) {// 判断是否有效及可以发送给客户端
////            // 客户端可写时
//            protocol.handleWrite(selectionKey);
//        }
    }

    /**
     * 把当前客户端信息 推送到其他客户端
     */
    private void dispatch(SocketChannel client, String info) throws IOException {
        Socket s = client.socket();
        String name =
                "[" + s.getInetAddress().toString().substring(1) + ":" + Integer.toHexString(client.hashCode()) + "]";
        if (!clientsMap.isEmpty()) {
            for (Map.Entry<String, SocketChannel> entry : clientsMap.entrySet()) {
                SocketChannel temp = entry.getValue();
                if (!client.equals(temp)) {
                    sBuffer.clear();
                    sBuffer.put((name + ":" + info).getBytes());
                    sBuffer.flip();
                    //输出到通道
                    temp.write(sBuffer);
                }
            }
        }
        clientsMap.put(name, client);
    }

    public static void main(String[] args) throws IOException {
        NIOSServer server = new NIOSServer(7777);
        server.listen();
    }
}
